/**
 * @module ChiVoxSDK/flash
 * @author Don Li <kai.li@chivox.com>
 * @licence
 * Copyright (c) 2017 - 2020 ChiVox Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import Utils       from '../utils';
import cfg         from '../config.json';
import { get_sig } from '../lib/public';

import swfobject   from './swfobject';

const FLASH_RECORDER_URL      = `${cfg.host}/static/swf/AudioRecorder4JS_${cfg.flash_version}.swf`;
const EXPRESS_INSTALL_SWF_URL = `${cfg.host}/static/swf/expressInstall.swf`;

/* chivox flash recorder callbacks */
window._cfr_callbacks = {
    decode_flash_msg:  (msg) => {
        return (typeof msg == "string") ? msg.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%") : msg;
    },

    on_mic_event: (code, message) => {
        let recorder = window.recorder;
        message      = window._cfr_callbacks.decode_flash_msg(message);

        console.debug('on_mic_event:', code, message);

        switch (code) {
            case '50001':
                recorder.mic_allow = true;
                recorder.showVolumeBar(recorder.CURRENT_VIEW, false);
                break;
            case '50002':
            case '50003':
            default:
                recorder.mic_allow = false;
                recorder.showVolumeBar(NO_VIEW, false);
                break;
        }

        recorder.refresh_status();

        if (recorder.options.onMicStatusChange)
            recorder.options.onMicStatusChange(code, JSON.parse(message));
    },

    on_connector_event: (code, message) => {
        console.debug('on_connector_event:', code, message);

        let recorder = window.recorder;
        message      = window._cfr_callbacks.decode_flash_msg(message);

        switch (code) {
            case '50100':
                recorder.connected = true;
                break;
            case '50109':
            case '50101':
            case '50103':
            case '50104':
            case 'connection.disconnected':
            default:
                recorder.connected = false;
                break;
        }

        recorder.refresh_status();

        if (recorder.options.onConnectorStatusChange)
            recorder.options.onConnectorStatusChange(code, JSON.parse(message));
    },

    on_recorder_core_event: (code, message, data) => {
        let recorder = window.recorder;

        message = window._cfr_callbacks.decode_flash_msg(message);
        message = JSON.parse(message);

        let options  = recorder.record_options;

        if (!options) {
            console.warn('on_core_event: recorder\'s record_options is null');
            return;
        }

        console.debug('on_core_event:', code, message);

        if (code == '50200') { //get score
            //if (message.Stream-Mode || message.tokenId)
            if (typeof message.result == 'string')
                message.result = JSON.parse(message.result);

            if (message.tokenId && message.tokenId != '' && message.eof == 1) { //score
                let result = message.result;
                if (typeof result.error != 'undefined' && typeof result.error.errMsg != 'undefined' && typeof result.errID != 'undefined') {
                    if (typeof options.onScoreError == 'function')
                        options.onScoreError(code, result.error);
                    else
                        console.error('[ on_core_event ] unknown onScoreError callback.', code, message);
                } else {
                    if (typeof options.onScore == 'function')
                        options.onScore(message);
                    else
                        console.error('[ on_core_event ] unknown onScore callback.', code, message);
                }

                recorder.recording = false;
            } else { //internal score result
                if (typeof options.onInternalScore == 'function')
                    options.onInternalScore(code, message);
                else
                    console.error('[ on_core_event ] unknown onInternalScore callback.', code, message);
            }

            if (typeof options.onPRCSucess == 'function')
                options.onPRCSucess(code, message, data);
        } else if (code == '50203') { //get prc request id from RPC
            if (typeof options.onPRCRequestIdGenerated == 'function')
                options.onPRCRequestIdGenerated(code, message, data);
        } else {
            if (typeof options.onScoreError == 'function')
                options.onScoreError(code, message);
            else
                console.error('[ on_core_event ] unknown onScoreError callback.', code, message);

            recorder.recording = false;
        }
    },

    on_record_event: (code, message) => {
        let recorder = window.recorder;
        let options  = (code == '50302' || code == '50303') ? recorder.replay_options : recorder.record_options;

        if (!options) {
            console.warn('on_record_event: recorder\'s record_options or replay_options is null');
            return;
        }

        message = window._cfr_callbacks.decode_flash_msg(message);
        message = JSON.parse(message);
        console.debug('on_record_event:', code, message);

        switch (code) {
            case '50304': //record id get
                recorder.last_record_id = message.tokenId;
                if (typeof options.onRecordIdGenerated == 'function')
                    options.onRecordIdGenerated(code, message);
                break;
            case '50301': //record start
                if (typeof options.onStart == 'function')
                    options.onStart(code, message);
                break;
            case '50300': //record stop
                if (typeof options.onStop == 'function')
                    options.onStop(code, message);
                break;
            case '50302': //replay stop
                if (typeof options.onStop == 'function')
                    options.onStop(code, message);
                break;
            case '50303': //replay stop
                if (typeof options.onStop == 'function')
                    options.onStop(code, message);
                break;
        }
    },

    on_factory_event: (code, message) => {
        let recorder = window.recorder;
        message      = window._cfr_callbacks.decode_flash_msg(message);

        console.debug('on_factory_event:', code, message);

        if (code == '50500') {
            code = '50100';
            recorder.connected = true;
        } else if (code == '50501') {
            code = '50104';
            recorder.connected = false;
        }

        recorder.refresh_status();

        if (recorder.options.onConnectorStatusChange)
            recorder.options.onConnectorStatusChange(code, JSON.parse(message));
    },

    on_flash_error: (code, message) => {
        message = window._cfr_callbacks.decode_flash_msg(message);
        console.error(code, message);
        window.recorder.options.onError(message);
    }
};

/**
 * Flash模式的录音机。<br/><br/>
 * 基于Flash Player 10.0及以上版本插件设计开发，用于音频录制，同时进行在线评测。
 */
export class FlashRecorder {
    /**
     * 创建一个Flash录音机。
     * @param {Object} options - 初始化参数。格式参见：{@link Default_ChiVoxRecorder_Options}
     */
    constructor(options) {
        this.CURRENT_VIEW = {
            meter:  { x: 0, y: -25, visible: false },
            slider: { x: 0, y: 20,  visible: false },
            viewer: { x: 0, y: 0,   visible: true  }
        };

        this.NO_VIEW = {
            meter:  { x: 0, y: -25, visible: true  },
            slider: { x: 0, y: 20,  visible: true  },
            viewer: { x: 0, y: 0,   visible: false }
        };

        window.recorder = this;

        this.options   = options;
        this.recorder  = document.getElementById(this.options.recorderid);

        this.recording = false;
        this.canRecord = false;
        this.loaded    = false;
        this.mic_allow = false;

        this.record_options = null;
        this.replay_options = null;
        this.last_record_id = '';

        this.__view    = {};
        this.__setview = false;
        this.get_sig   = get_sig;

        this.__embedswf();
    }

    __on_flash_loaded() {
        this.canRecord = true;
        this.loaded    = true;
        this.recorder  = document.getElementById(this.options.recorderid); //refresh

        if (this.mic_allow || !this.__setview) {
            try {
                this.recorder.showVolumeBar(this.__view);
            } catch (e) {
                console.warn(e);
            }
        }

        if (this.options.onInit)
            this.options.onInit(0, null);
    }

    __embedswf() {
        let params  = { allowScriptAccess: 'always', wmode: this.options.wmode };
        let attribs = { id: this.options.recorderid, name: this.options.recorderid, wmode: this.options.wmode };
        let vars    = {
            showFlash: this.options.showFlash,
            backgroundColor: this.options.backgroundColor,
            coreTimeout: cfg.core_timeout,
            appKey: this.options.appKey,
            server: this.options.server,
            sig: 'temp',
            timestamp: '0',
            jssdkVersion: cfg.version,
            flashLoadEventHandler: (code, message) => { window.g_flashrecorder_is_load = true; },
            connectorEventHandler: "window._cfr_callbacks.on_connector_event",
            coreRequesterEventHandler: "window._cfr_callbacks.on_recorder_core_event",
            micEventHandler: "window._cfr_callbacks.on_mic_event",
            recorderEventHandler: "window._cfr_callbacks.on_record_event",
            factoryEventHandler: "window._cfr_callbacks.on_factory_event",
            errorEventHandler: "window._cfr_callbacks.on_flash_error"
        };

        if (cfg.debug)
            vars.jsLogCallback = (msg) => { console.debug(msg); };

        if (this.options.bak_server)
            vars.back_server = this.options.bak_server;

        let _secs_passed = 0;
        let _interval    = 500;
        let _check_loading_status = () => {
            _secs_passed += _interval;

            if (window.g_flashrecorder_is_load) {
                this.__on_flash_loaded();
                window.g_flashrecorder_is_load = null;
            } else if (_secs_passed > cfg.load_flash_timeout) {
                this.options.onError(`timeout(${_secs_passed / 1000} secs) when loading swf: ${FLASH_RECORDER_URL}`);
                window.g_flashrecorder_is_load = null;
            } else {
                setTimeout(_check_loading_status, _interval);
            }
        };

        window.g_flashrecorder_is_load = false;
        window.jsIsReady               = () => { return true; };
        swfobject.embedSWF(FLASH_RECORDER_URL, this.options.recorderid, this.options.width, this.options.height, '10.0.0', EXPRESS_INSTALL_SWF_URL,
                vars, params, attribs);
        setTimeout(_check_loading_status, _interval);
    }

    /** 显示波形图。*/
    showVolumeBar(options, setview) {
        let params = {
            meter:  { x: 0, y: -25, visible: false },
            slider: { x: 0, y: 20,  visible: false },
            viewer: { x: 0, y: 0,   visible: true  }
        };

        if (typeof options != "undefined") {
            params = Utils.extend(options, params, true);
        }

        this.__setview = setview;
        this.__view    = params;

        if (this.loaded && (this.mic_allow || !setview)) {
            try {
                this.recorder.showVolumeBar(params);
            } catch (e) { }
        }
    }

    refresh_status() {
        this.canRecord = (this.loaded && this.mic_allow);
    }

    __startRecord(params) {
        params.serverParams.applicationId = this.options.appKey;
        params.serverParams.sig           = params.sig;
        params.serverParams.timestamp     = params.timestamp;

        if (Utils.in_array(params.serverParams.coreType, ['en.word.score', 'en.sent.score', 'en.sent.rescore',
                    'cn.word.score', 'cn.sent.score'])) {
            let score = params.serverParams.scoreType || params.serverParams.rank || 100;;
        } else if (params.serverParams.coreType == 'en.sent.rec') {
            params.serverParams.language = 'english';
        } else if (params.serverParams.coreType == 'cn.sent.rec') {
            params.serverParams.language = 'chinese';
        }

        if (!params.serverParams.userId)
            params.serverParams.userId = this.options.userId;

        var p = {
            serverParam: params.serverParams,
            recordLength: params.duration,
            virtualDirectory: '',
            isVADEnabled: false,
            silenceLevel: '0',
            playDing: params.playDing
        };

        this.record_options = params;

        try {
            var r = this.recorder.startRecord(p);
            console.info('begin recording..', r);
        } catch (e) { console.error(e); }

        this.recording = true;
    }

    /**
     * 开始录音。
     *
     * @param {Object} params - 录音时所需的参数。参数有：
     * @param {string} params.duration - 录音时长，单位：毫秒。
     * @param {boolean} params.playDing - 录音前是否播放“ding”。
     * @param {Object} params.serverParams - 录音参数。详见；{@link Default_ChiVoxRecorder_Options}
     * @param {Callback} params.onRecordIdGenerated - 开始录音后生成recordId后的Callback。格式：(code, message) => { var lastRecordID = message.recordId; }
     * @param {Callback} params.onStart - 开始录音后的Callback。格式：() => { }
     * @param {Callback} params.onStop - 录音结束后的Callback。格式：(code, message) => { }
     * @param {Callback} params.onInternalScore - 录音中服务器Push回来的中间评分结果的Callback。格式：(code, message) => { }
     * @param {Callback} params.onScore - 服务器评分结果返回后的Callback。格式：(data) => { }
     * @param {Callback} params.onScoreError - 服务器评分出错返回后的Callback。格式：(code, message) => { }
     */
    record(params) {
        if (this.recording) {
            options.onError('please wait the last record end!');
            return;
        }

        if (typeof this.options.sig != 'undefined') {
            params.sig = this.options.sig;
            params.timestamp = this.options.timestamp;
        }

        var current_t = Math.floor(new Date().getTime() / 1000);

        if (params.sig !== '' && params.timestamp !== undefined && parseInt(params.timestamp) >= (current_t - cfg.timeout)) {
            this.__startRecord(params);
        } else {
            this.get_sig(params);
        }

    }

    /** 停止录音 */
    stopRecord() {
        this.recorder.stopRecord({});
    }

    /**
     * 开始回放最后一次录音。
     *
     * @param {Object} options - 回放时所需的参数。参数有：
     * @param {string} options.recordId - 要回放的录音recordId。
     * @param {callback} options.onStop - 回放完成的Callback，格式：() => {}。
     */
    startReplay(params) {
        if (this.last_record_id) {
            this.replay_options = params;

            var p = { tokenId: this.last_record_id };

            try {
                console.debug('startReplay:', this.recorder.startReplay(p));
            } catch (e) { }
        } else {
            this.options.onError('no last_record_id.');
        }
    }

    /** 停止回放。*/
    stopReplay() {
        try {
            this.recorder.stopReplay({});
        } catch (e) { }
    }

    /** 重置录音机。*/
    reset() {
        this.stopReplay();
        this.stopRecord();
    }
}

export default FlashRecorder
